COMP3151/9154 Foundations of Concurrency
Term 2, 2022

Wednesday Code and Notes (Week 4)

Table of Contents

1 Java code

1.1 Dining philosophers, first attempt

import java.util.concurrent.Semaphore;

class Philosopher extends Thread {
    String name;
    Semaphore left_fork;
    Semaphore right_fork;

    Philosopher(String name, Semaphore left_fork, Semaphore right_fork) {
        this.name = name;
        this.left_fork = left_fork;
        this.right_fork = right_fork;        
    }

    public void run () {
        while(true) {
            System.out.println(name + " thinks...");
            try {
                Thread.sleep(150);
            }
            catch(InterruptedException e) {
            }

            left_fork.acquireUninterruptibly();
            System.out.println(name + " grabbed a fork...");
            try {
                Thread.sleep(150);
            }
            catch(InterruptedException e) {
            }
            right_fork.acquireUninterruptibly();
            System.out.println(name + " grabbed another fork...");            
            System.out.println(name + " eating...");
            try {
                Thread.sleep(150);
            }
            catch(InterruptedException e) {
            }
            System.out.println(name + " releases forks...");
            left_fork.release();
            right_fork.release();
        }
    }
}

public class Dinphil {
    public static void main(String[] args) {
        // we want binary semaphores that have 1 free ticket initially
        // we don't care about liveness here, only deadlock
        Semaphore fork1 = new Semaphore(1,false);
        Semaphore fork2 = new Semaphore(1,false);
        Semaphore fork3 = new Semaphore(1,false);                
        Thread t1 = new Philosopher("Hegel",fork1,fork2);
        Thread t2 = new Philosopher("Plato",fork2,fork3);
        Thread t3 = new Philosopher("Averroes",fork3,fork1);

        t1.start();
        t2.start();
        t3.start();                
    }
}

/* We're deadlocked :(
   Suggestion:
   - make "grab both forks" a critical section
     with another semaphore, shared between
     everybody
   - ordering the forks
     In the system as a whole, we'll have
         n   locks  (or semaphores)
     each process requires some subset of the
     locks

     Globally order all the locks
     Total order:
        fork1 < fork2 < fork3
     Enforce discipline:
        everybody needs to claim the "smallest"
        lock first

     If everybody claims locks in an order
     consistent w/ the total order,
     deadlock is impossible.

     If P is stuck, that means somebody is
     hogging a higher lock.
     But the process hogging the *highest* lock
     can't be stuck.
 */

1.2 Dining philosophers, second attempt

This solution fixes the deadlock issue by enforcing a total order on locks.

import java.util.concurrent.Semaphore;

class Philosopher extends Thread {
    String name;
    Semaphore left_fork;
    Semaphore right_fork;

    Philosopher(String name, Semaphore left_fork, Semaphore right_fork) {
        this.name = name;
        this.left_fork = left_fork;
        this.right_fork = right_fork;        
    }

    public void run () {
        while(true) {
            System.out.println(name + " thinks...");
            try {
                Thread.sleep(150);
            }
            catch(InterruptedException e) {
            }

            left_fork.acquireUninterruptibly();
            System.out.println(name + " grabbed a fork...");
            try {
                Thread.sleep(150);
            }
            catch(InterruptedException e) {
            }
            right_fork.acquireUninterruptibly();
            System.out.println(name + " grabbed another fork...");            
            System.out.println(name + " eating...");
            try {
                Thread.sleep(150);
            }
            catch(InterruptedException e) {
            }
            System.out.println(name + " releases forks...");
            left_fork.release();
            right_fork.release();
        }
    }
}

public class Dinphil2 {
    public static void main(String[] args) {
        // we want binary semaphores that have 1 free ticket initially
        // we don't care about liveness here, only deadlock
        Semaphore fork1 = new Semaphore(1,false);
        Semaphore fork2 = new Semaphore(1,false);
        Semaphore fork3 = new Semaphore(1,false);
        // fork1 < fork2 < fork3
        Thread t1 = new Philosopher("Hegel",fork1,fork2);
        Thread t2 = new Philosopher("Plato",fork2,fork3);
        Thread t3 = new Philosopher("Averroes",fork1,fork3);

        t1.start();
        t2.start();
        t3.start();                
    }
}

1.3 Rendezvous, first attempt

import java.util.concurrent.Semaphore;

class RendezvousThread extends Thread {
    String name;

    public RendezvousThread(String name) {
        this.name = name;
    }

    public void run() {
        System.out.println(name + " first statement");
        System.out.println(name + " second statement");        
    }
}

public class Rendezvous {
    public static void main(String[] args) {
        Thread t1 = new RendezvousThread("Bertram");
        Thread t2 = new RendezvousThread("Agatha");

        t1.start();
        t2.start();
    }
}

1.4 Rendezvous, second attempt

import java.util.concurrent.Semaphore;

class RendezvousThread extends Thread {
    String name;
    Semaphore s1;
    Semaphore s2;

    public RendezvousThread(String name, Semaphore s1, Semaphore s2) {
        this.name = name;
        this.s1 = s1;
        this.s2 = s2;
    }

    public void run() {
        System.out.println(name + " first statement");
        // here, we want to: unblock the other proc
        // wait until the other proc unblocks us
        s1.release();
        s2.acquireUninterruptibly();
        System.out.println(name + " second statement");        
    }
}

public class Rendezvous2 {
    public static void main(String[] args) {
        Semaphore s1 = new Semaphore(0);
        Semaphore s2 = new Semaphore(0);
        Thread t1 = new RendezvousThread("Bertram",s1,s2);
        Thread t2 = new RendezvousThread("Agatha",s2,s1);

        t1.start(); // start means
                    // execute run() in a new thread
        t2.start();
    }
}

1.5 Rendezvous, attempt 3

Here's a hack we can do to achieve the same thing with one Java semaphore, because of the oddity that we can have initially negative number of permits.

import java.util.concurrent.Semaphore;

class RendezvousThread extends Thread {
    String name;
    Semaphore s1;

    public RendezvousThread(String name, Semaphore s1) {
        this.name = name;
        this.s1 = s1;
    }

    public void run() {
        System.out.println(name + " first statement");
        // here, we want to: unblock the other proc
        // wait until the other proc unblocks us
        s1.release();
        s1.acquireUninterruptibly();
        System.out.println(name + " second statement");
        s1.release();
    }
}

public class Rendezvous3 {
    public static void main(String[] args) {
        Semaphore s1 = new Semaphore(-1);
        Thread t1 = new RendezvousThread("Bertram",s1);
        Thread t2 = new RendezvousThread("Agatha",s1);

        t1.start(); // start means
                    // execute run() in a new thread
        t2.start();
    }
}

2 Notes

Locks:
  - lock()  acquire()      --- pre-protocol
    unlock() release()     --- post-protocol

  Locks: only one process can claim the lock at one time.

Semaphores:
  - multiple process can pass through a semaphore at the same time.

  Binary semaphores: (v,L)
   - v number of free process slots, free tickets
   - L processes on waiting list

  For binary semaphores, v is always either 0 or 1.

  A lock is a special case of a semaphore, namely a binary semaphore.

            wait        signal
acronyms    P           V
dutch       passering   vrijgaven
java        acquire     release


Difference:
- when we signal, we unblocke someone from the waiting set.
  But: who gets unblocked? And to where do they get unblocked?

busy-wait semaphores
aka very weak semaphores
- You're unblocked to the state *before* the wait.
  You have to try another wait.
  This means you can be preempted by people who weren't
  on the waiting list when the signal came.
- Who gets unblocked? Any member of L.

weak semaphores
- When a signal happens, one process, any process, is unblocked.
- You're released to the state *after* the wait.
  That is: you're released into the "critical section",
   and don't have to retry the wait.

strong semaphores
- Processes are released in FIFO order.
- You're released to the state *after* the wait.
  That is: you're released into the "critical section",
   and don't have to retry the wait.


Q: What impact does weak vs. busy-wait have on eventual entry?
   (for 2 processes)

    Busy-wait (aka very weak) doesn't guarantee eventual entry.
    Weak semaphores do guarantee eventual entry.

Q: What about for 3 or more procs?

    Weak semaphores don't guarantee eventual entry.
    If you're unlucky, every signal happens when you're sharing the
     waiting room with someone else, who push ahead.
Q: but what about under weak fairness?
    Still no :(

    Weak fairness guarantees that if an action is *always* enabled,
    then it will eventually happen.
    (If a process is always not blocked, it will eventually
     make progress).
    But: you're not *always* unblocked.
    You're *always eventually* unblocked.

    Suppose p1 is on the waiting list.
      p2 and p3 are forever taking turns entering the CS.
    p1 will:
    - sit on the wait list (be blocked)
    - be unblocked whenever anyone's about to execute the pre-protocol.
      (unblocked: you could possibly be chosen to go ahead)
    - in the next state, you're blocked again.
    - repeat ad infinitum

Q: what about under strong fairness?
A: Yes, strong fairness is enough (see above).


(1) v = 1 + #signal(S) - #wait(S)
(2) v ≥ 0
(3) #CS = #wait(S) - #signal(S)

Substitute (1) into (2)

1 + #signal(S) - #wait(S) ≥ 0
(add and subtract on both sides)
1 ≥ #wait(s) - #signal(S) = #CS

#CS ≤ 1

2022-08-05 Fri 16:47

Announcements RSS